package com.hx.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hx.entry.ExcelFieldOfEquipment;
import com.hx.entry.TrainingEquipment;
import com.hx.listener.ImportDataSyncListener;
import com.hx.mapper.TrainingEquipmentMapper;
import com.hx.service.ITrainingEquipmentService;
import com.hx.util.SpringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.TaskExecutor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;

@Slf4j
@Service
public class TrainingEquipmentService extends ServiceImpl<TrainingEquipmentMapper, TrainingEquipment> implements ITrainingEquipmentService {
    @Resource
    private TaskExecutor taskExecutor;
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;
    @Value("${system.config.excel-equipment-head-rownum:}")
    private int headNum;
    @Value("${system.config.excel-equipment-sheet-name:}")
    private String sheetName;

    private List<CompletableFuture> list = CollUtil.newArrayList();
    private AtomicLong successCounter = new AtomicLong(0); // 成功入库数据量统计
    private AtomicLong errorCounter = new AtomicLong(0); // 成功入库数据量统计

    private void printProgress() {
        if (successCounter.get() % 1000 == 0) {
            String format = StrUtil.format("已成功处理：{} 条", successCounter.get());
            notice(format);
        }
    }

    @Override
    public void processData(ExcelFieldOfEquipment data) {
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
            SpringUtil.getBean(ITrainingEquipmentService.class).saveData(data);
            successCounter.getAndIncrement();
            printProgress();
        }).exceptionally(ex -> {
            //log.error("异常：{}",ex.getMessage());
            //System.out.println("有一个future失败了: " + ex.getMessage());
            errorCounter.getAndIncrement();
            return null;
        });
        list.add(voidCompletableFuture);
    }

    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void saveData(ExcelFieldOfEquipment data) {
        try {
            TrainingEquipment base = BeanUtil.toBean(data, TrainingEquipment.class);
            this.save(base);
        } catch (Exception e) {
            if (e instanceof DuplicateKeyException) {
                if (e.getCause() instanceof SQLIntegrityConstraintViolationException) {
                    SQLIntegrityConstraintViolationException cause = (SQLIntegrityConstraintViolationException) e.getCause();
                    String format = StrUtil.format("第[{}行]数据入库有误，原因：数据重复，{}", data.getRowNum(), cause.getMessage());
                    notice(format);
                    //log.error("{}", cause.getMessage());
                }
            } else if (e instanceof DataIntegrityViolationException) {
                if (e.getCause() instanceof SQLException) {
                    SQLException ee = (SQLException) e.getCause();
                    String format = StrUtil.format("第[{}行]数据入库有误，原因：{}", data.getRowNum(), ee.getMessage());
                    notice(format);
                }
            } else {
                log.error("导入数据异常：{}", e.getMessage());
            }
            throw e;
        }
    }

    @Override
    public void notice(String message) {
        if (StrUtil.startWith(message, "导入完成")) {
            CompletableFuture[] arr = new CompletableFuture[list.size()];
            list.toArray(arr);
            CompletableFuture.allOf(arr).thenAccept(it -> {
                String message1 = StrUtil.format(message + "（成功入库：{} 条，失败：{} 条）", successCounter.get(), errorCounter.get());
                log.info("{}", message1);
                simpMessagingTemplate.convertAndSend("/topic/ws", message1);
                successCounter.set(0);
                errorCounter.set(0);
            });
//            CompletableFuture.allOf(arr).thenAccept(v -> {
//                return message;
//            }).exceptionally(ex -> {
//                System.out.println("有一个future失败了: " + ex.getMessage());
//                return null;
//            });
        } else {
            simpMessagingTemplate.convertAndSend("/topic/ws", message);
        }
    }

    private boolean isSheetExist(MultipartFile file, String sheetName) {
        try (InputStream is = file.getInputStream()) {
            ExcelReader build = EasyExcel.read(is).build();
            List<ReadSheet> sheetNames = build.excelExecutor().sheetList();
            return sheetNames.stream().anyMatch(sheet -> sheet.getSheetName().equalsIgnoreCase(sheetName));
        } catch (Exception e) {
            return false;
        }
    }

    @Async
    @Override
    public void readExcel(MultipartFile file) {
        try {
            List<String> sheetNames = ExcelUtil.getReader(file.getInputStream()).getSheetNames();
            if (sheetNames.contains(sheetName)) {
                EasyExcel.read(
                                file.getInputStream(), ExcelFieldOfEquipment.class,
                                new ImportDataSyncListener<ExcelFieldOfEquipment>(data -> this.processData(data), message -> this.notice(message), null, ExcelFieldOfEquipment.class)
                        )
                        .headRowNumber(headNum)
                        .sheet(sheetName)
                        .doRead();
            } else {
                notice(StrUtil.format("【{}】Sheet页不存在，请指定EXCEL的sheet页名称", sheetName));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static boolean isAllFieldsNull(Object obj, String... exclusionColumns) throws IllegalAccessException {
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (StrUtil.equalsAny(field.getName(), exclusionColumns)) {
                continue;
            }
            field.setAccessible(true);
            Object value = field.get(obj);
            if (ObjectUtil.isNotEmpty(value)) {
                return false;
            }
        }
        return true;
    }
}